/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
**/

package com.chasekernan.hxnova.server.functions;

import com.chasekernan.hxnova.core.fleets.Fleet;
import com.chasekernan.hxnova.core.dataholders.Global;

/**
    Extends the fleet class to temporarily have [hasMoved] and [fleetTarget] 
    fields.
**/
typedef FleetInfo = {>Fleet,
    var hasMoved : Bool;
    var fleetTarget : FleetInfo;
}

/**
    Handles all fleet movements, especially cases where multiple fleets are 
    targeting eachother.
**/
class FleetMovement {
    
    /**
        The number of times a situation where there is circular targeting is
        iterated.
    **/
    public static var CIRCULAR_TARGET_ROUNDS = 10;
    
    public static function run() {
        var fleets = new Array<FleetInfo>();
        
        for (fleet in Global.fleets) {
            var info : FleetInfo = cast fleet;
            info.hasMoved = false;
            info.fleetTarget = null;
            fleets[info.id] = info;
        }
        
        for (fleet in fleets) {
            //if the fleet has no target movement, skip it.
            //these aren't removed from the list because other fleets still 
            //might target them.
            if (fleet.waypoints == null || fleet.waypoints.length == 0) {
                fleet.hasMoved = true;
            }
            
            //if another fleet was targeting this one, it might have already
            //moved.
            if (fleet.hasMoved) continue;
            
            handleFleet(fleet, fleets);
        }
        
        //check to see if a fleet's waypoint1 has been reached and add its
        //data back into [Global.fleets].
        for (fleet in fleets) {
            if (fleet.waypoints == null || fleet.waypoints.length == 0) {
                continue;
            }
            
            var reachedWaypoint = switch(fleet.waypoints[0].target) {
                
                case Location(v):
                    fleet.location.distanceTo(v) < 0.1;
                    
                case StarTarget(s):
                    var starLocation = Global.stars.getStarById(s.id)
                            .location.clone();
                    fleet.location.distanceTo(starLocation) < 0.1;
                    
                case FleetTarget(f):
                    var targetFleet = Global.fleets.getById(f.id, f.owner.id);
                    fleet.location.distanceTo(targetFleet.location) < 0.1;
            };
            
            if (reachedWaypoint) fleet.waypoint0 = fleet.waypoints.shift().task;
            
            Global.fleets.addParticle(cast Fleet);
        }
    }
    
    //this can get very recursive if there are thousands of fleets targeting
    //in a straight chain, so the recursion depth might need to be checked and
    //dealt with.
    private static function handleFleet(fleet : FleetInfo, 
                                        fleets : Array<FleetInfo>,
                                        ?previousTargets : List<FleetInfo>) {
        switch(fleet.waypoints[0].target) {
                
            case Location(v):
                fleet.move(v, fleet.waypoints[0].warp);
                fleet.hasMoved = true;
                    
            case StarTarget(s):
                var starLocation = Global.map.stars[s.id].location.clone();
                fleet.move(starLocation, fleet.waypoints[0].warp);
                fleet.hasMoved = true;
                    
            case FleetTarget(f):
                var targetFleet : FleetInfo = null;
                for (otherFleet in fleets) {
                    if (otherFleet.id == f.id && 
                            otherFleet.owner.id == f.owner.id) {
                        targetFleet = otherFleet;
                        break;
                    }
                }
                if (targetFleet == null) {
                    throw "Could not locate target fleet with id " + f.id;
                }
                    
                //if the target has already moved, then this fleet
                //can go ahead and move to its new location.
                //TODO: When scanning is added, the target fleet needs to 
                //be checked to see if its still in scanner range.
                if (targetFleet.hasMoved) {
                    fleet.move(targetFleet.location.clone(), 
                               fleet.waypoints[0].warp);
                    fleet.hasMoved = true;
                } else {
                    //this is where it could get ugly.
                    //if its just a straight chain of fleets that don't 
                    //target each other (ie A -> B -> C -> (Star)), then
                    //C moves, then B, then A. if they target each other
                    //(ie A -> B -> C -> A), then they're handed off to
                    //[handleSelfTargetFleets].
                    
                    fleet.fleetTarget = targetFleet;
                    
                    if (previousTargets == null) {
                        previousTargets = new List();
                        previousTargets.push(fleet);
                        
                        handleFleet(targetFleet, fleets, previousTargets);
                        
                        //if the fleet still hasn't moved after recursively 
                        //calling [handleFleet], then its a straight chain, so 
                        //just move towards the target.
                        if (!fleet.hasMoved) {
                            fleet.move(targetFleet.location.clone(), 
                                       fleet.waypoints[0].warp);
                            fleet.hasMoved = true;
                        }
                    } else {
                        //check to see if this is targeting any of the fleets
                        //already in the previous targets.
                        //if a circular target is found, that section of
                        //previous targets is removed and dealt with.
                        
                        var notTargeted = new List<FleetInfo>();
                        
                        //no built in way to copy a list so this is a little 
                        //hack.
                        var copy = previousTargets.filter(
                                function(fi : FleetInfo) { return true; } );
                        
                        for (target in copy) {
                            if (targetFleet == target) {
                                previousTargets.add(fleet);
                                handleCircularTargeting(previousTargets);
                                return;
                            } else {
                                //these are still part of a straight chain, so
                                //add them to the new list of previous targets
                                notTargeted.add(previousTargets.pop());
                            }
                        }
                        
                        handleFleet(targetFleet, fleets, notTargeted);
                        
                        //if this fleet still hasn't moved, then move towards
                        //the target fleet
                        if (!fleet.hasMoved) {
                            fleet.move(targetFleet.location.clone(), 
                                       fleet.waypoints[0].warp);
                            fleet.hasMoved = true;
                        }
                    }
                }
        }
    }
    
    private static function handleCircularTargeting(
            fleets : List < FleetInfo > ) {
        for (i in 0...CIRCULAR_TARGET_ROUNDS) {
            for (fleet in fleets) {
                //move 1 / CIRCULAR_TARGET_ROUNDS % towards its target.
                fleet.move(fleet.fleetTarget.location.clone(), 
                           fleet.waypoints[0].warp, 
                           1.0 / CIRCULAR_TARGET_ROUNDS);
            }
        }
        
        //mark all as moved
        for (fleet in fleets) fleet.hasMoved = true;
    }

}